using System;
using System.IO;
using Org.BouncyCastle.Asn1;

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Date;

namespace Org.BouncyCastle.Bcpg.OpenPgp
{
	/// <remarks>A PGP signature object.</remarks>
	public class PgpSignature
	{
		public const int BinaryDocument = 0x00;
		public const int CanonicalTextDocument = 0x01;
		public const int StandAlone = 0x02;

		public const int DefaultCertification = 0x10;
		public const int NoCertification = 0x11;
		public const int CasualCertification = 0x12;
		public const int PositiveCertification = 0x13;

		public const int SubkeyBinding = 0x18;
		public const int PrimaryKeyBinding = 0x19;
		public const int DirectKey = 0x1f;
		public const int KeyRevocation = 0x20;
		public const int SubkeyRevocation = 0x28;
		public const int CertificationRevocation = 0x30;
		public const int Timestamp = 0x40;

		private readonly SignaturePacket	sigPck;
		private readonly int				signatureType;
		private readonly TrustPacket		trustPck;

		private ISigner	sig;
		private byte	lastb; // Initial value anything but '\r'

		internal PgpSignature(
			BcpgInputStream bcpgInput)
			: this((SignaturePacket)bcpgInput.ReadPacket())
		{
		}

		internal PgpSignature(
			SignaturePacket sigPacket)
			: this(sigPacket, null)
		{
		}

		internal PgpSignature(
			SignaturePacket	sigPacket,
			TrustPacket		trustPacket)
		{
			if (sigPacket == null)
				throw new ArgumentNullException("sigPacket");

			this.sigPck = sigPacket;
			this.signatureType = sigPck.SignatureType;
			this.trustPck = trustPacket;
		}

		private void GetSig()
		{
			this.sig = SignerUtilities.GetSigner(
				PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm));
		}

		/// <summary>The OpenPGP version number for this signature.</summary>
		public int Version
		{
			get { return sigPck.Version; }
		}

		/// <summary>The key algorithm associated with this signature.</summary>
		public PublicKeyAlgorithmTag KeyAlgorithm
		{
			get { return sigPck.KeyAlgorithm; }
		}

		/// <summary>The hash algorithm associated with this signature.</summary>
		public HashAlgorithmTag HashAlgorithm
		{
			get { return sigPck.HashAlgorithm; }
		}

		public void InitVerify(
			PgpPublicKey pubKey)
		{
			lastb = 0;
			if (sig == null)
			{
				GetSig();
			}
			try
			{
				sig.Init(false, pubKey.GetKey());
			}
			catch (InvalidKeyException e)
			{
				throw new PgpException("invalid key.", e);
			}
		}

		public void Update(
			byte b)
		{
			if (signatureType == CanonicalTextDocument)
			{
				doCanonicalUpdateByte(b);
			}
			else
			{
				sig.Update(b);
			}
		}

		private void doCanonicalUpdateByte(
			byte b)
		{
			if (b == '\r')
			{
				doUpdateCRLF();
			}
			else if (b == '\n')
			{
				if (lastb != '\r')
				{
					doUpdateCRLF();
				}
			}
			else
			{
				sig.Update(b);
			}

			lastb = b;
		}

		private void doUpdateCRLF()
		{
			sig.Update((byte)'\r');
			sig.Update((byte)'\n');
		}

		public void Update(
			params byte[] bytes)
		{
			Update(bytes, 0, bytes.Length);
		}

		public void Update(
			byte[]	bytes,
			int		off,
			int		length)
		{
			if (signatureType == CanonicalTextDocument)
			{
				int finish = off + length;

				for (int i = off; i != finish; i++)
				{
					doCanonicalUpdateByte(bytes[i]);
				}
			}
			else
			{
				sig.BlockUpdate(bytes, off, length);
			}
		}

		public bool Verify()
		{
			byte[] trailer = GetSignatureTrailer();
			sig.BlockUpdate(trailer, 0, trailer.Length);

			return sig.VerifySignature(GetSignature());
		}

		private void UpdateWithIdData(
			int		header,
			byte[]	idBytes)
		{
			this.Update(
				(byte) header,
				(byte)(idBytes.Length >> 24),
				(byte)(idBytes.Length >> 16),
				(byte)(idBytes.Length >> 8),
				(byte)(idBytes.Length));
			this.Update(idBytes);
		}

		private void UpdateWithPublicKey(
			PgpPublicKey key)
		{
			byte[] keyBytes = GetEncodedPublicKey(key);

			this.Update(
				(byte) 0x99,
				(byte)(keyBytes.Length >> 8),
				(byte)(keyBytes.Length));
			this.Update(keyBytes);
		}

		/// <summary>
		/// Verify the signature as certifying the passed in public key as associated
		/// with the passed in user attributes.
		/// </summary>
		/// <param name="userAttributes">User attributes the key was stored under.</param>
		/// <param name="key">The key to be verified.</param>
		/// <returns>True, if the signature matches, false otherwise.</returns>
		public bool VerifyCertification(
			PgpUserAttributeSubpacketVector	userAttributes,
			PgpPublicKey					key)
		{
			UpdateWithPublicKey(key);

			//
			// hash in the userAttributes
			//
			try
			{
				MemoryStream bOut = new MemoryStream();
				foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
				{
					packet.Encode(bOut);
				}
				UpdateWithIdData(0xd1, bOut.ToArray());
			}
			catch (IOException e)
			{
				throw new PgpException("cannot encode subpacket array", e);
			}

			this.Update(sigPck.GetSignatureTrailer());

			return sig.VerifySignature(this.GetSignature());
		}

		/// <summary>
		/// Verify the signature as certifying the passed in public key as associated
		/// with the passed in ID.
		/// </summary>
		/// <param name="id">ID the key was stored under.</param>
		/// <param name="key">The key to be verified.</param>
		/// <returns>True, if the signature matches, false otherwise.</returns>
		public bool VerifyCertification(
			string			id,
			PgpPublicKey	key)
		{
			UpdateWithPublicKey(key);

			//
			// hash in the id
			//
			UpdateWithIdData(0xb4, Strings.ToByteArray(id));

			Update(sigPck.GetSignatureTrailer());

			return sig.VerifySignature(GetSignature());
		}

		/// <summary>Verify a certification for the passed in key against the passed in master key.</summary>
		/// <param name="masterKey">The key we are verifying against.</param>
		/// <param name="pubKey">The key we are verifying.</param>
		/// <returns>True, if the certification is valid, false otherwise.</returns>
		public bool VerifyCertification(
			PgpPublicKey	masterKey,
			PgpPublicKey	pubKey)
		{
			UpdateWithPublicKey(masterKey);
			UpdateWithPublicKey(pubKey);

			Update(sigPck.GetSignatureTrailer());

			return sig.VerifySignature(GetSignature());
		}

		/// <summary>Verify a key certification, such as revocation, for the passed in key.</summary>
		/// <param name="pubKey">The key we are checking.</param>
		/// <returns>True, if the certification is valid, false otherwise.</returns>
		public bool VerifyCertification(
			PgpPublicKey pubKey)
		{
			if (SignatureType != KeyRevocation
				&& SignatureType != SubkeyRevocation)
			{
				throw new InvalidOperationException("signature is not a key signature");
			}

			UpdateWithPublicKey(pubKey);

			Update(sigPck.GetSignatureTrailer());

			return sig.VerifySignature(GetSignature());
		}

		public int SignatureType
		{
			get { return sigPck.SignatureType; }
		}

		/// <summary>The ID of the key that created the signature.</summary>
		public long KeyId
		{
			get { return sigPck.KeyId; }
		}

		[Obsolete("Use 'CreationTime' property instead")]
		public DateTime GetCreationTime()
		{
			return CreationTime;
		}

		/// <summary>The creation time of this signature.</summary>
		public DateTime CreationTime
		{
			get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); }
		}

		public byte[] GetSignatureTrailer()
		{
			return sigPck.GetSignatureTrailer();
		}

		/// <summary>
		/// Return true if the signature has either hashed or unhashed subpackets.
		/// </summary>
		public bool HasSubpackets
		{
			get
			{
				return sigPck.GetHashedSubPackets() != null
					|| sigPck.GetUnhashedSubPackets() != null;
			}
		}

		public PgpSignatureSubpacketVector GetHashedSubPackets()
		{
			return createSubpacketVector(sigPck.GetHashedSubPackets());
		}

		public PgpSignatureSubpacketVector GetUnhashedSubPackets()
		{
			return createSubpacketVector(sigPck.GetUnhashedSubPackets());
		}

		private PgpSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks)
		{
			return pcks == null ? null : new PgpSignatureSubpacketVector(pcks);
		}

		public byte[] GetSignature()
		{
			MPInteger[] sigValues = sigPck.GetSignature();
			byte[] signature;

			if (sigValues != null)
			{
				if (sigValues.Length == 1)    // an RSA signature
				{
					signature = sigValues[0].Value.ToByteArrayUnsigned();
				}
				else
				{
					try
					{
						signature = new DerSequence(
							new DerInteger(sigValues[0].Value),
							new DerInteger(sigValues[1].Value)).GetEncoded();
					}
					catch (IOException e)
					{
						throw new PgpException("exception encoding DSA sig.", e);
					}
				}
			}
			else
			{
				signature = sigPck.GetSignatureBytes();
			}

			return signature;
		}

		// TODO Handle the encoding stuff by subclassing BcpgObject?
		public byte[] GetEncoded()
		{
			MemoryStream bOut = new MemoryStream();

			Encode(bOut);

			return bOut.ToArray();
		}

		public void Encode(
			Stream outStream)
		{
			BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream);

			bcpgOut.WritePacket(sigPck);

			if (trustPck != null)
			{
				bcpgOut.WritePacket(trustPck);
			}
		}

		private byte[] GetEncodedPublicKey(
			PgpPublicKey pubKey) 
		{
			try
			{
				return pubKey.publicPk.GetEncodedContents();
			}
			catch (IOException e)
			{
				throw new PgpException("exception preparing key.", e);
			}
		}
	}
}
